<?php
/**
 * Contains the Event class and includes all the right files.
 *
 * @package    event
 * @subpackage event
 * @author 	   Gerrit Jan van Ahee <gerrit.jan@yes2web.nl>
 * @copyright  2010-2011 Yes2web B.V.
 * @license    MIT License http://www.opensource.org/licenses/mit-license.php
 * @version    1.0.1
 * @link 	   www.yes2web.nl
 * @see        Event_Handler, Event_Exception
 */
// require the proper event classes
$sEventPackageDir = dirname(__FILE__).DIRECTORY_SEPARATOR;
require $sEventPackageDir.'handler.class.php';
require $sEventPackageDir.'exception.class.php';
// require the log package
$sEventPackageDir .= 'log'.DIRECTORY_SEPARATOR;
require $sEventPackageDir.'device.class.php';
require $sEventPackageDir.'handler.class.php';
require $sEventPackageDir.'message.class.php';
// cleanup the global scope
unset($sEventPackageDir);

/**
 * The Event class. Upon constructing an Event_Event the new instance will
 * automatically fire. This is done by signaling the Event_Handler statically about
 * its existence. This will in turn notify the actual event handlers that a
 * potentially interesting event has taken place. This implies that triggering events
 * is done by simply constucting an Event_Event:
 * <code>
 * // constructs the event and signals potential handlers
 * new Event_Event('event-name');
 * </code>
 *
 * Classes triggering events need not extends specific classes, making this design
 * more flexible than e.g. the Dispatcher-Observer type patterns. The Event_Event
 * will determine itself who has triggered it, and will provide this information to
 * the Event_Handler when needed.
 *
 * When an event is triggered by an instance, i.e. from a non-static method call,
 * that instance is available through the {@link getSource()} method. When triggered
 * during a static method call, this method provides the name (String) of the
 * triggering class.
 *
 * Besides the instance triggering the Event, the arguments provided to the method
 * triggering the event can also be accessed through the {@link getArgs} method.
 * <code>
 * // no need to extend anything, allowing you to extend whatever
 * // else you would really like to
 * class TriggeringClass
 * {
 *  // ...
 * 	public function someTrigger($a, $b, $c){
 * 		new Event_Event('trigger');
 * 	}
 * 	// ...
 * }
 * $oTriggeringClassInstance->someTrigger('one', 2, 'THREE');
 * </code>
 * <p>
 * A call to {@link getArgs()} will provide
 * </p>
 * <code>
 * array(
 * 	'one', 2, 'THREE'
 * );
 * </code>
 *
 * Events can be given additional information by adding an array when constructing
 * it:
 * <code>
 * new Event_Event(
 * 	'some-event',
 * 	array(
 * 		'key1' => 'value1',
 * 		'key2' => 'value2',
 * 	)
 * );
 * </code>
 *
 * which is available through the {@link getParams()} method. Populate this array
 * with any information relevant to a handler.
 *
 * Finally, the event keeps a prevent-default flag that may be checked by the
 * triggering instance. This way a handler could prevent the default action from
 * happening (if the triggering object supports this). This is facilitated by the
 * {@link preventDefault()} and {@link isDefaultPrevented()} methods:
 *
 * <code>
 * class TriggeringClass extends Your_Favorite_ORM
 * {
 * 	// ...
 * 	public function delete()
 * 	{
 * 		$oEvent = new Event_Event('delete');
 * 		if (false === $oEvent->isDefaultPrevented()) parent::delete();
 * 	}
 * 	// ...
 * }
 * </code>
 *
 * An Event_Handler would simply have to call
 * <code>
 * $oEvent->preventDefault();
 * </code>
 *
 * @package    event
 * @subpackage event
 * @author 	   Gerrit Jan van Ahee <gerrit.jan@yes2web.nl>
 * @copyright  2010-2011 Yes2web B.V.
 * @license    MIT License http://www.opensource.org/licenses/mit-license.php
 * @version    1.0.1
 * @link 	   www.yes2web.nl
 * @see        Event_Handler, Event_Exception
 */
class Event_Event {
	/**
	 * Shorthand for triggering an Event.
	 *
	 * This method provides a slightly simpler/faster/easier way to trigger an
	 * Event (faster as is: less code). This mostly applies to situations where you
	 * would like to use the prevent default flag:
	 * <code>
	 * 	if (false === Event_Event::trigger('before-delete')->isDefaultPrevented()) {
	 * 		// delete
	 * 		new Event_Event('delete'); // no "default", so don't care
	 * 	}
	 * 	else {
	 * 		// one of the event handlers wished the deletion to be prevented
	 * 	}
	 * </code>
	 *
	 * Off course, the same can be done using
	 * <code>
	 * $oEvent = new Event_Event('before-delete');
	 * 	if ($oEvent->isDefaultPrevented()) { ... }
	 * </code>
	 * but for all us lazy people out there...
	 *
	 * @param String $sType   Type of the event
	 * @param array  $amParam additional parameters, default empty list
	 * @param array  $amTrace trace to this call, defaults to debug_backtrace()
	 * @return Event_Event The triggered event
	 */
	public static function trigger($sType, array $amParam = null) {
		$amTrace = debug_backtrace();
		return new Event_Event($sType, $amParam, $amTrace);
	}

	// end of static members

	/**
	 * @var Mixed Object or String depending on triggering method: static: String
	 * (classname), other: Object (instance)
	 */
	protected $mSource;
	/**
	 * @var String identifier of the type of this event
	 */
	protected $sType;
	/**
	 * @var Mixed[] list of additional values provided to this event by the triggerer
	 */
	protected $amParam;
	/**
	 * @var Mixed[] the arguments that were provided to the source when called at the
	 * time the event was triggered
	 */
	protected $amArgv = array();
	/**
	 * @var Mixed[] debug trace
	 */
	protected $amTrace;
	/**
	 * @var Mixed[] $amData List of variables that can be attached from the Event Handler
	 * to the Event, in order to pass back information to the triggering instance
	 */
	protected $amData = array();
	/**
	 * Handlers can signal triggerers to prevent their default action, however they
	 * will have to check this
	 * first.
	 *
	 * @see preventDefault()
	 * @see isDefaultPrevented()
	 * @var bool prevent default?
	 */
	protected $bDefaultPrevented = false;

	/**
	 * Construct an new Event by providing at least a type
	 *
	 * @param String $sType   Type of the event
	 * @param array  $amParam additional parameters, default empty list
	 * @param array  $amTrace trace to this call, defaults to debug_backtrace()
	 */
	public function __construct($sType, array $amParam = null, array $amTrace = null) {
		$this->sType = $sType;
		$this->amParam = isset($amParam)? $amParam:array();
		$this->amTrace = isset($amTrace)? $amTrace:debug_backtrace();

		$amSource = array_shift($this->amTrace);	// remove reference to this call
		$amSource = array_shift($this->amTrace);	// remove reference to source

		// retrieve the source from trace
		if (isset($amSource['object'])) {
			// not present when the event originates from a static call
			$this->mSource = $amSource['object'];
		} elseif (isset($amSource['class'])) {
			$this->mSource = $amSource['class'];
		}

		if (isset($amSource['args'])) {
			$this->amArgv = $amSource['args'];
		}

		Event_Handler::signalEvent($this);
	}

	/**
	 * Get all or specific data: Allows any Handler to pass back information to
	 * the triggering class or instance,
	 *
	 * Example use:
	 * <code>
	 * // in the triggering instance:
	 *	$oEvent = Event::trigger('some-event');
	 *	// ...
	 *	$sAffectedByEvent = $oEvent->getData('affected');
	 *	// or
	 *	$amEventData = $oEvent->getData();
	 *
	 * // in the handler (onSomeEvent):
	 *	$oEvent->setData('affected', 15);
	 * </code>
	 *
	 * @see setData
	 * @param String $sDataName the name of the specific data-entry requested.
	 * @return Mixed The entire data set when no specific entry was required, null
	 * if the specific entry was not found or the value of the entry,
	 */
	public function getData($sDataName = null) {
		$mReturn = null;
		if (is_null($sDataName)) {
			$mReturn = $this->amData;
		}
		elseif (isset($this->amData[(string) $sDataName])) {
			$mReturn = $this->amData[(string) $sDataName];
		}
		return $mReturn;
	}

	/**
	 * Get all args that have been passed to the method that has thrown this event
	 *
	 * @return Mixed[] Argument vector provided to the method triggering this event
	 */
	public function getArgs() {
		return $this->amArgv;
	}

	/**
	 * Get the instance (or classname in case of static call) triggering this Event
	 *
	 * @return Mixed Either the instance (Object) or the classname (String)
	 */
	public function getSource() {
		return $this->mSource;
	}

	/**
	 * Get any additional parameters provided by the triggering instance/object (the
	 * Source)
	 *
	 * @return Mixed[] list of parameters
	 */
	public function getParams() {
		return $this->amParam;
	}

	/**
	 * Get the type of $this Event
	 *
	 * @return String The name of this event
	 */
	public function getType() {
		return $this->sType;
	}

	/**
	 * Has some handler signalled the prevention of the default actions?
	 *
	 * @see Event_Event::preventDefault
	 * @return bool Should the default action be prevented by the triggering method
	 */
	public function isDefaultPrevented() {
		return $this->bDefaultPrevented;
	}
	/**
	 * Raise the flag that the default action following the event should be prevented
	 *
	 * The flag can only be raised, never lowered
	 *
	 * @return bool Should the default action be prevented by the triggering method
	 */
	public function preventDefault() {
		$this->bDefaultPrevented = true;
		return $this;
	}

	/**
	 * Set specific data: Allows any Handler to pass
	 * back information to the triggering class or instance,
	 *
	 * @see getData
	 * @param String $sDataName the name of the specific data-entry to set.
	 * @param String $mDataValue the value of the specific data-entry to set.
	 */
	public function setData($sDataName, $mDataValue) {
		$this->amData[(string) $sDataName] = $mDataValue;
	}
}
